package org.jederik.studip.android.sync;

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.apache.http.ConnectionReuseStrategy;
import org.apache.http.HttpResponse;
import org.apache.http.protocol.HttpContext;
import org.jederik.android.provider.StudIP;
import org.jederik.android.provider.CalendarProvider.*;
import org.jederik.android.provider.StudIP.*;
import org.jederik.studip.android.Const;
import org.jederik.studip.android.conn.RESTClient;
import org.jederik.studip.android.conn.exceptions.NotFoundException;
import org.jederik.studip.android.provider.DatabaseHelper;
import org.jederik.studip.android.view.Preferences;

import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;

/**
 * Diese Klasse bietet Methoden zur Synchronisation der lokalen Datenbank mit
 * dem Server. Sie wird ausschließig vom {@link org.jederik.studip.sync.SyncService}
 * verwendet.
 * @author Jann-Frederik Laß
 *
 */
public class Processor {

	private static final String TAG = "Processor";
	

	// Verweis auf den Applikationskontext:
	private static Context context;
	
	// Singleton
	private static Processor instance;
	
	// Zum Anfordern des Singleton:
	public static Processor getInstance(Context context) {
		Processor.context = context;
		if (instance == null) {
			instance = new Processor();
			Log.d(TAG, "New instance created");
		}
		return instance;
	}
	
	// Helfer für die Kommunikation mit dem Webservice:
	private RESTClient mRESTClient;
	
	// Für den Zugriff auf den Calendar Content Provider und zum Benachrichtigen 
	// der ContentObserver:
	private ContentResolver mResolver;
	
	// Für den Zugriff auf die lokale Datenbank:
	private SQLiteDatabase db;
	
	public Processor() {
		mRESTClient = new RESTClient(context);
		mResolver = context.getContentResolver();
		db = new DatabaseHelper(context).getWritableDatabase();
	}

	/**
	 * Führt die Synchronisation vom Feed und den besuchten Veranstaltungen durch,
	 * aktualisiert, welche Objekte schon vom Nutzer gesichtet wurden, und importiert
	 * ggf. den Stundenplan.
	 */
	public void synchronize() {
		
		// Feed synchonisieren:
		synchronizeFeed();
		
		// Aktualisieren, welche Objekte als gelesen markiert wurden:
		synchronizeVisitedObjects();
		
		// Liste der besuchten Veranstaltungen aktualisieren:
		synchronizeMycourses();
		
		// Stundenplanimport vom Nutzer gewünscht?
		boolean importSchedule = Preferences
		.getPreferences(context)
		.getBoolean(Preferences.IMPORT_SCHEDULE, false);

		// Stundenplan importieren:
		if (importSchedule) synchronizeSchedule();
		
	}
	
	/*
	 * Liest aus den Nutzereinstellungen, wie weit die angezeigten Elemente
	 * zurückreichen sollen.
	 */
	private int getBeginOfTimespan() {
		
		return (int) (System.currentTimeMillis()/1000L) 
			- Integer.parseInt(
				Preferences.getPreferences(context)
				.getString(Preferences.FEED_TIMESPAN, "604800")
			);
	}

	/** Bringt den Feed auf den aktuellen Stand. */
	public void synchronizeFeed() {

		Log.i(TAG, "Synchronizing Feed...");
		
		// Bis wann sollen die Feed-Einträge zurückreichen?
		int beginOfTimespan = getBeginOfTimespan();
		
		// Wann wurde der Feed zum letzten Mal synchronisiert?
		int lastFeedSync = context.getSharedPreferences(Const.PREFS_NAME, 0)
							.getInt(Preferences.LAST_FEED_SYNC, -1);
		
		// Das Maximum (der spätere der beiden Zeitpunkte) wird genommen:
		int since;
		if (lastFeedSync > beginOfTimespan) since = lastFeedSync;
		else since = beginOfTimespan;

		
		// Alle neuen Änderungen seit dem ausgewählten Zeitpunkt vom Server laden:
		Iterator<ContentValues> it = mRESTClient.get(Feed.WS_PATH + "/" + since);

		
		// Die heruntergeladenen Feed-Einträge werden nun durchlaufen und in die
		// Datenbank eingetragen:
		
		ContentValues entry;		// Der aktuelle Datensatz
		int chdate;					// Hilfsvariable
		final String whereClause = Feed.OBJECT_ID + "=?";		
									// WHERE-Argument, für die Datenbankabrage 
		int affectedRows;			// Hilfsvariabe
		boolean receivedChanges = false;	// Hilfsvariable, um zu registrieren,
											// ob Änderungen gefunden wurden
		int newestChange = lastFeedSync;	// Die bisher aktuellste Änderung
											// ist die von der letzten Synchronisation
		String objectType, objectId;		// Hilfsvariablen
		
		db.beginTransaction();
		
		while (it.hasNext()) {
			
			// Nächsten eintrag auswählen:
			entry = it.next();
			
			// Sync-Status auf SYNCHRONIZED setzen:
			entry.put(StudIP._SYNC_STATE, StudIP.STATE_SYNCHRONIZED);
			
			// Falls bereits ein Eintrag zu diesem Objekt vorhanden ist,
			// überschreiben:
			affectedRows = db.update(
					Feed.DB_TABLE_NAME, 
					entry, 
					whereClause, 
					new String[] { entry.getAsString(Feed.OBJECT_ID) }
			);
			
			// Sonst neuen anlegen:
			if (affectedRows==0)
				db.insert(Feed.DB_TABLE_NAME, null, entry);
			
			// Falls bisher aktuellste Änderung, Datum merken:
			chdate = entry.getAsInteger(Feed.DATE).intValue();
			if (newestChange < chdate) newestChange = chdate;

			// Resource, auf die sich der Feed-Eintrag bezieht, als ausstehend
			// markieren:
			objectType = entry.getAsString(Feed.OBJECT_TYPE);
			objectId = entry.getAsString(Feed.OBJECT_ID);
			setSyncState(
					StudIP.defaultTables.get(objectType), 
					objectId, 
					StudIP.STATE_PENDING, 
					StudIP.defaultUris.get(objectType)
			);

			// Merken, dass es Änderungen gab:
			if (receivedChanges == false) receivedChanges = true;
			
		}
		
		db.setTransactionSuccessful();
		db.endTransaction();
		
		// Veraltete Einträge aus Datenbank entfernen:
		removeOldFeedEntries();
		
		// Eventuelle ContentObserver über die Änderung benachrichtigen:
		if (receivedChanges)
			mResolver.notifyChange(Feed.CONTENT_URI, null);
		
		// Zeitpunkt der aktuellsten empfangenen Änderung in den
		// Einstellungen merken:
		SharedPreferences.Editor editor = 
			context.getSharedPreferences(Const.PREFS_NAME, 0).edit();
		editor.putInt(Preferences.LAST_FEED_SYNC, newestChange);
		editor.commit();
		
	}
	
	/** Feed-Einträge, die älter als die vom Nutzer eingestellte Zeitspanne
	 * sind (standartmäßig 7 Tage), werden gelöscht, um Speicher zu sparen. */
	private void removeOldFeedEntries() {
		
		db.delete(
				Feed.DB_TABLE_NAME, 
				Feed.DATE + "<" + getBeginOfTimespan(), 
				null
		);
		
	}
	
	public void synchronizeSchedule() {
		
		Log.i(TAG, "Synchronizing Schedule...");
		

		// In den Einstellungen gewählten Kalender auswählen:
		String calendarId = Preferences.getPreferences(context).getString(Preferences.SELECT_CALENDAR, null);
		
		// Wurde Kalender gewählt?
		if (calendarId==null) {
			// TODO Aufforderung zum wählen eines Kalenders
			Log.e(TAG, "No Calendar selected!");
			Toast.makeText(context, "Wählen sie einen Kalender!", Toast.LENGTH_LONG).show();
			return;
		}
		
		// Existiert gewählter Kalender?
		if (!mResolver.query(
				Uri.withAppendedPath(Calendars.CONTENT_URI, calendarId), 
				null, null, null, null).moveToFirst()) {
			
			// TODO Aufforderung zum wählen eines Kalenders
			Log.e(TAG, "Calendar " + calendarId + " doesn't exist!");
			Toast.makeText(context, "Kalender wurde gelöscht. Wählen Sie einen neuen!", Toast.LENGTH_LONG).show();
			return;
		}
		
		int lastScheduleSync = context.getSharedPreferences(Const.PREFS_NAME, 0).getInt(Preferences.LAST_SCHEDULE_SYNC, -1);
		Iterator<ContentValues> it = mRESTClient.get(ExportSchedule.WS_PATH + "/" + lastScheduleSync);
		
		String eventId = null;
		int maxChdate = -1;
		int chdate;
		ContentValues calendarEntry = new ContentValues();
		Cursor event;
		boolean entryFound;
		final String SELECT_BY_OBJECT_ID = StudIP.OBJECT_ID + "=?";
		String objectId;
		ContentValues values;
		
		db.beginTransaction();

		while (it.hasNext()) {

			values = it.next();
			objectId = values.getAsString(StudIP.OBJECT_ID);
			
			// Event in Kalender eintragen:
			calendarEntry.clear();
			calendarEntry.put(Events.TITLE, values.getAsString(ExportSchedule.TITLE));
			calendarEntry.put(Events.EVENT_LOCATION, values.getAsString(ExportSchedule.LOCATION));
			calendarEntry.put(Events.DESCRIPTION, values.getAsString(ExportSchedule.DESCRIPTION));
			calendarEntry.put(Events.DTSTART, values.getAsString(ExportSchedule.START) + "000");
			calendarEntry.put(Events.DTEND, values.getAsString(ExportSchedule.END) + "000");
			calendarEntry.put(Events.HAS_EXTENDED_PROPERTIES, "1");
			calendarEntry.put(Events._SYNC_DIRTY, "0");
			calendarEntry.put(Events.CALENDAR_ID, calendarId);
			
			// In der Datenbank nach einem bereits vorhandenen Vermerk zu dem 
			// Stundenplan-Eintrag suchen:
			event = db.query(
					Schedules.DB_TABLE_NAME, 
					new String[] { Schedules.EVENT_ID }, 
					SELECT_BY_OBJECT_ID, 
					new String[] { objectId },
					null, null, null);
			
			// Falls DB-Eintrag vorhanden:
			if (event.moveToFirst()) {
				
				// ID des korrespondierenden Kalender-Eintrags erhalten:
				eventId = event.getString(event.getColumnIndex(Schedules.EVENT_ID));
				
				// Versuchen, den Kalendereintrag zu updaten:
				entryFound = mResolver.update(Uri.withAppendedPath(Events.CONTENT_URI, eventId), calendarEntry, null, null) > 0;
				
				// Falls Kalender-Eintrag nicht mehr vorhanden (z.B. von Nutzer gelöscht):
				if (!entryFound) {
					
					// Neu anlegen:
					eventId = mResolver.insert(Events.CONTENT_URI, calendarEntry).getLastPathSegment();
					
					// ID des neuen Kalender-Eintrags in DB vermerken:
					calendarEntry.clear();
					calendarEntry.put(Schedules.EVENT_ID, eventId);
					db.update(
							Schedules.DB_TABLE_NAME, 
							calendarEntry, 
							SELECT_BY_OBJECT_ID, 
							new String[] { objectId }
					);
					
				}
			}
			
			// Falls DB-Eintrag nicht vorhanden:
			else {

				// Kalender-Eintrag erzeugen:
				eventId = mResolver.insert(Events.CONTENT_URI, calendarEntry).getLastPathSegment();
				
				// ID des Kalender-Events zur Stud.IP-ID in Datenbank merken:
				calendarEntry.clear();
				calendarEntry.put(Schedules.EVENT_ID, eventId);
				calendarEntry.put(StudIP.OBJECT_ID, objectId);
				db.insert(Schedules.DB_TABLE_NAME, null, calendarEntry);

			}
			
			event.close();

			// Stud.IP-ID in extended properties notieren:
			calendarEntry.clear();
			calendarEntry.put(ExtendedProperties.EVENT_ID, eventId);
			calendarEntry.put(ExtendedProperties.NAME, "studip_id");
			calendarEntry.put(ExtendedProperties.VALUE, objectId);
			mResolver.insert(ExtendedProperties.CONTENT_URI, calendarEntry);
			
			chdate = Integer.parseInt(values.getAsString(ExportSchedule.CHDATE));
			if (chdate > maxChdate) maxChdate = chdate;

			Log.d(TAG, "Added event " + eventId + " (chdate=" + chdate + ")");

		}
		
		db.setTransactionSuccessful();
		db.endTransaction();
		
//		cursor.close();
		
		mResolver.notifyChange(Events.CONTENT_URI, null);
		// TODO StudipProvider auch benachrichtigen?
		
		if (maxChdate==-1) return;
		SharedPreferences.Editor editor = context.getSharedPreferences(Const.PREFS_NAME, 0).edit();
		editor.putInt(Preferences.LAST_SCHEDULE_SYNC, maxChdate);
		editor.commit();
		
	}

	/**
	 * Aktualisiert die Liste "Meine Veranstaltungen"
	 */
	public void synchronizeMycourses() {
		
		Log.i(TAG, "Synchronizing Mycourses...");
		
		// Liste vom Server laden:
		Iterator<ContentValues> it = mRESTClient.get(Mycourses.WS_PATH);

		db.beginTransaction();

		// Alte Liste Löschen:
		db.delete(Mycourses.DB_TABLE_NAME, null, null);
		
		// Heruntergeladene Kurse in lokale Datenbank eintragen:
		ContentValues values;
		while (it.hasNext()) {
			values = it.next();
			values.put(StudIP._SYNC_STATE, StudIP.STATE_SYNCHRONIZED);
			db.insert(Mycourses.DB_TABLE_NAME, null, values);
			Log.d(TAG, "Added: " + values);
		}
		
		db.setTransactionSuccessful();
		db.endTransaction();
		
		// ContentObserver benachrichtigen:
		mResolver.notifyChange(Mycourses.CONTENT_URI, null);

	}
	
	/**
	 * Synchonisiert eine einzelne Resource.
	 * @param uri Beschreibt die Resource
	 * @param dbTableName Name der Tabelle der lokalen Datenbank, in der die Resource steht.
	 * @param restPath URL-Pfad zur Webservice-Methode
	 * @param objectId Stud.IP-ID der Resource
	 */
	public void synchronizeResource(Uri uri, String dbTableName, String restPath, String objectId) {
		
		// SQL-WHERE-Argument zum referenzieren der Resource in der Datenbank:
		String whereClause = StudIP.OBJECT_ID + "='" + objectId + "'";

		// Setze Status auf SYNCHRPNIZING:
		setSyncState(dbTableName, objectId, StudIP.STATE_SYNCHRONIZING, uri);

		try {

			ContentValues resource;
			
			try {
				
				// Resource herunterladen und Serverantwort parsen:
				resource = mRESTClient.get(restPath).next();
				
			} catch (NoSuchElementException e) {
				
				// Wenn der Iterator leer ist, wurde die Resource nicht gefunden:
				throw new NotFoundException(e);
				
			}

			// Heruntergeladene Daten in Lokale Datenbank eintragen:
			db.update(dbTableName, resource, whereClause, null);
			
			// Status auf SYNCHRONIZED setzen:
			setSyncState(dbTableName, objectId, StudIP.STATE_SYNCHRONIZED, uri);
			
			// ContentObserver benachrichtigen:
			context.getContentResolver().notifyChange(uri, null);

		} catch (RuntimeException e) {
			
			// Bei Fehler Status auf FAILED setzen:
			setSyncState(dbTableName, objectId, StudIP.STATE_FAILED, uri);
			
			// Nach gewisser Zeit neu probieren:
			Message msg = Message.obtain(mRetryHandler);
			Bundle data = new Bundle(Bundle.EMPTY);
			data.putString(DB_TABLE_NAME, dbTableName);
			data.putString(OBJECT_ID, objectId);
			data.putParcelable(URI, uri);
			msg.setData(data);
			mRetryHandler.sendMessageDelayed(msg, Const.RETRY_AFTER);
			
			throw e;
		}
	}
	
	// Schlüssel für die Felder, die dem mRetryHandler die Resource beschreiben,
	// deren Synchronisation neu versucht werden soll:
	private static final String DB_TABLE_NAME = "dbTableName";
	private static final String OBJECT_ID = "objectId";
	private static final String URI = "uri";
	
	/**
	 * Setzt eine Resource, deren Synchronisation fehlgeschlagen ist, nach Ablauf
	 * einer gewissen Zeitspanne wieder auf (STATE_PENDING), damit die Synchronisation
	 * neu versucht werden kann. Sollte eine Komponente einen ContentObserver
	 * auf diese Resource angesetzt haben, so wird diese informiert und kann
	 * die Daten neu anfordern, wodurch eine neue Synchronisation bewirkt wird.
	 * Ist dies nicht der Fall, so wird die Resource neu synchronisiert, wenn
	 * sie das nächste mal beim StudipProvider angefordert wird.
	 */
	private Handler mRetryHandler = new Handler() {
		
		@Override
		public void handleMessage(Message msg) {
			
			try {

				String dbTableName = msg.getData().getString(DB_TABLE_NAME);
				String objectId = msg.getData().getString(OBJECT_ID);
				Uri uri = msg.getData().getParcelable(URI);
				
				setSyncState(dbTableName, objectId, StudIP.STATE_PENDING, uri);
				
			}
			
			catch (Exception e) {
				super.handleMessage(msg);
			}
		}
		
	};

	/** Teilt dem Server mit, dass eine Resource geöffnet wurde, sodass sie dort
	 * als gelsen markiert werden kann.
	 * @param objectId Stud-IP-ID der Resource
	 */
	public void synchronizeObjectVisit(String objectId) {
		
		String whereClause = StudIP.OBJECT_ID + "='" + objectId + "'";
		
		// Feed-Eintrag für Objekt Abrufen:
		Cursor feedEntry = db.query(
				Feed.DB_TABLE_NAME, 
				new String[] { Feed.OBJECT_TYPE, Feed.READ }, 
				whereClause, 
				null, null, null, null);
		
		// Spaltenindizes abrufen:
		int IDX_READ = feedEntry.getColumnIndex(Feed.READ);
		int IDX_OBJECT_TYPE = feedEntry.getColumnIndex(Feed.OBJECT_TYPE);

		// Objekt gelesen?
		if (feedEntry.moveToFirst() && 
			feedEntry.getString(IDX_READ).equals("1"))
		
		{

			ContentValues state = new ContentValues();

			// Synchronisationsstatus auf DELETING setzen:
			state.put(StudIP._SYNC_STATE, StudIP.STATE_SYNCHRONIZING);
			db.update(Feed.DB_TABLE_NAME, state, whereClause, null);
			
			// Resource auf dem Server als gelesen markieren:
			String objectType = feedEntry.getString(IDX_OBJECT_TYPE);
			mRESTClient.delete(Objects.WS_PATH + "/" + objectType + "/" + objectId);
			
			// Synchronisationsstatus auf SYNCHRONIZED setzen:
			state.put(StudIP._SYNC_STATE, StudIP.STATE_SYNCHRONIZED);
			db.update(Feed.DB_TABLE_NAME, state, whereClause, null);
			
		}
		
		feedEntry.close();
		
	}
	
	/** 
	 * Überprüft beim Server, welche Resourcen (beispielsweise über die Website)
	 * geöffnet wurden, und markiert diese auch lokal als gelesen.
	 * @param objectId
	 */
	public void synchronizeVisitedObjects() {
		
		int lastVisitedObjectsSync = context
			.getSharedPreferences(Const.PREFS_NAME, 0)
			.getInt(Preferences.LAST_VISITED_OBJECTS_SYNC, 0);
		int beginOfTimespan = getBeginOfTimespan();
		int since;
		if (beginOfTimespan > lastVisitedObjectsSync) since = beginOfTimespan;
		else since = lastVisitedObjectsSync;
		
		Iterator<ContentValues> it = mRESTClient.get(VisitedObjects.WS_PATH + "/" + since);
		
		ContentValues read = new ContentValues(1);
		read.put(Feed.READ, true);

		boolean receivedChanges = false;
		while (it.hasNext()) {

			receivedChanges = true;
			
			// Werte auslesen:
			ContentValues values = it.next();
			String objectId = values.getAsString(VisitedObjects.OBJECT_ID);
			int visitDate = values.getAsInteger(VisitedObjects.VISITDATE);
			
			// Objekt als gelesen markieren:
			db.update(
					Feed.DB_TABLE_NAME, 
					read, 
					Feed.OBJECT_ID + "='" + objectId + "'", 
					null
			);
			
			// Ggf. LAST_VISITED_OBJECTS_SYNC aktualisieren
			if (visitDate > lastVisitedObjectsSync) 
				lastVisitedObjectsSync = visitDate;
			
		}
		
		
		// Eventuelle ContentObserver benachrichtigen:
		if (receivedChanges)
			mResolver.notifyChange(Feed.CONTENT_URI, null);
		
		// Zeitpunkt des aktuellsten synchronisierten geänderten Objekts in den
		// Einstellungen merken:
		SharedPreferences.Editor editor = 
			context.getSharedPreferences(Const.PREFS_NAME, 0).edit();
		editor.putInt(Preferences.LAST_VISITED_OBJECTS_SYNC, lastVisitedObjectsSync);
		editor.commit();
		
	}
	
	/**
	 * Überträgt eine geschriebene Nachricht an den Server.
	 * @param id ID des Datenbankeintrags in dem Nachrichtenentwurf gespeichert ist.
	 */
	public void synchronizeSentMessage(long id) {
		
		String selection = MessagesSent._ID + "=" + id;
		Cursor message = db.query(
				MessagesSent.DB_TABLE_NAME, 
				null, 
				selection, 
				null, null, null, null);
		
		// Synchronisation ausstehend?
		if (message.moveToFirst() && message.getInt(message.getColumnIndex(StudIP._SYNC_STATE)) == StudIP.STATE_PENDING) {
			
			Uri uri = Uri.withAppendedPath(MessagesSent.CONTENT_URI, ""+id);
			
			// Sync-Status auf SYNCHRONIZING setzen:
			ContentValues state = new ContentValues();
			state.put(StudIP._SYNC_STATE, StudIP.STATE_SYNCHRONIZING);
			db.update(MessagesSent.DB_TABLE_NAME, state, selection, null);
			context.getContentResolver().notifyChange(uri, null);

			try {
				message.moveToPrevious();
				mRESTClient.post(
						MessagesSent.WS_PATH,
						Messages.INPUT_COLUMNS, 
						message
				);
			}
			
			catch (RuntimeException e) {
				
				// Im Fehlerfall Status auf PENDING zurücksetzen:
				state.put(StudIP._SYNC_STATE, StudIP.STATE_PENDING);
				db.update(MessagesSent.DB_TABLE_NAME, state, selection, null);
				context.getContentResolver().notifyChange(uri, null);

				message.close();
				
				// Fehler weiterreichen:
				throw e;
			}

			// Hat alles geklappt, Status auf SYNCHRONIZED setzen und 
			// Versanddatum notieren:
			state.put(StudIP._SYNC_STATE, StudIP.STATE_SYNCHRONIZED);
			state.put(MessagesSent.DATE, System.currentTimeMillis()/1000);
			db.update(MessagesSent.DB_TABLE_NAME, state, selection, null);
			context.getContentResolver().notifyChange(uri, null);
			
		}
		
		message.close();
		
	}

	/**
	 * Setzt den Synchronisationsstatus einer Resource. Falls die Resource noch
	 * nicht existiert, wird ein Platzhalter mit dem entsprechenden Status
	 * in die Datenbank eingefügt.
	 * @param dbTableName Name der Tabelle
	 * @param objectId Stud.IP-ID der Resource
	 * @param syncState Zu setzender Status. Mögliche Werte sind 
	 * {@link org.jederik.studip.android.StudIP.STATE_PENDING}, 
	 * {@link org.jederik.studip.android.StudIP.STATE_SYNCHRONIZING} und
	 * {@link org.jederik.studip.android.StudIP.STATE_SYNCHRONIZED}.
	 * {@link org.jederik.studip.android.StudIP.STATE_FAILED}.
	 * @param uri Beschreibt die Resource
	 * @return {@code true} wenn die Resource existierte, 
	 * {@code false} wenn ein Platzhalter eingefügt wurde
	 */
	public boolean setSyncState(String dbTableName, String objectId,
			int syncState, Uri uri) {

		// WHERE-Phrase vorbereiten:
		String whereClause = StudIP.OBJECT_ID + "='" + objectId + "'";
		
		// Eintrag vorbereiten:
		ContentValues state = new ContentValues();
		state.put(StudIP._SYNC_STATE, syncState);
		int affectedRows = db.update(dbTableName, state, whereClause, null);

		// Nicht Vorhanden?
		if (affectedRows == 0) {
			
			// Platzhalter einfügen:
			state.put(StudIP.OBJECT_ID, objectId);
			db.insert(dbTableName, null, state);
			
			return false;
			
		}

		// ContentObserver benachrichtigen:
		context.getContentResolver().notifyChange(uri, null);
		
		return true;
	}
}
